// Resolve noConflict Mode
(function($) {
	
// Make sure console is defined
if(!window.console)
	window.console = {log: function(){}};

// All global variables declared here

var g = {
	ui				: {
		index			: 0,
		array			: null,
		cycle			: {
			init_nspc		: {
				name			: null,
				id				: 1,
				length			: 0,
				selector		: function(){ return $(this).siblings(); }
			},
			default_nspc	: {
				name			: 'e',
				selector		: function(){ return $(this).siblings('.e'); }
			},
			bpOutput_nspc	: {
				name			: 'container',
				selector		: function(){
					if ($(this).attr('id') == 'output')
						return $(this);
						
					var e = $('.e').filter(function(){
						return !$(this).data('location').rendered;
					});
					return $(this).siblings().add(e);
				}
			}
			
		},
		types			: ['header','content','sidebar','footer']
		/*types			: [
			{
				name	: 'header',
				limit	: 1
			}, {
				name	: 'content',
				limit	: 0
			}, {
				name	: 'sidebar',
				limit	: 0
			}, {
				name	: 'footer',
				limit	: 1
			}
		]*/
	},
	ir				: {
		selected		: [],
		old_labels			: [],
		labels			: {
			a		: [],
			add		: function( name ) {
				if( name !== '')
					g.ir.labels.a.push( nameToId( name ) );
			},
			has		: function( name ) {
				return g.ir.labels._has( name );
			},
			remove	: function( name ) {
				var index = g.ir.labels._has( name, true );
				
				if ( index !== false )
					g.ir.labels.a.splice( index, 1 );
			},
			_has	: function( id, get_index ) {
				get_index = get_index || false;
				id = nameToId( id );

				var index = $.inArray( id, g.ir.labels.a );

				if ( -1 === index )
					return false;
				else
					return (get_index) ? index : true;
			}
		},
		fn				: {
			disableCanvas	: function() {
				$('#dialog-overlay').dialog('open'); // Disable canvas
			},
			enableCanvas	: function() {
				$('#dialog-overlay').dialog('close'); // Enable canvas
			},
			canvasLoadBegin	: function() {
				console.log('canvasLoadBegin');
				g.ir.fn.disableCanvas();
				g.ir.fn.loadingShow();
			},
			canvasLoadEnd	: function() {
				console.log('canvasLoadEnd');
				g.ir.fn.loadingHide();
				g.ir.fn.enableCanvas();
			},
			loadingShow		: function() {
				var load = $('#inspector-loading').show().children('div');
				
				load.css( 'margin-top', load.height() * -1 );
				
				var dist = ( $('#inspector').height() / 2 ) - ( load.height() / 2 );
				console.log(dist,'in',$('#inspector').height(),'load',load.height());
				
				load.hide().css('margin-top', dist +'px').fadeIn(200);
			},
			loadingHide		: function() {
				$('#inspector-loading').fadeOut(400);
			},
			getTabAnchor	: function( id ) {
				return $('#tabs-inspector > ul:first > li > a[href="'+ id +'"]');
			},
			bindTabSelect	: function( id, data, callback ) { // Like $().bind(), data is optional.
				if( ! callback ) {
					callback = data;
					data = undefined;
				}
				
				console.log('TAB SELECT!!!', 'anchor', g.ir.fn.getTabAnchor( id ),'event', ($('#tabs-inspector').tabs('option', 'event') + '.tabs'), 'data',data,'callback',callback);
				
				g.ir.fn.getTabAnchor( id ).bind( $('#tabs-inspector').tabs('option', 'event') + '.tabs', data, callback );
			}
		}
	},
	bp				: {
		column_count	: 24,
		column_width	: 30,
		gutter_width	: 10,
		page_width		: 950,
		input_padding	: 5,
		input_border	: 1,
		// Variables added for editor support
		row_count		: 13,
		row_height		: 40,
		gutter_height	: 10,
		page_height		: 640
	},
	theme			: {
		name		: 'Elastic',
		title		: 'Elastic',
		path		: 'elastic',
		install		: true,
		uri			: 'http://koopersmith.wordpress.com/',
		description	: 'A theme generated by the elastic framework.',
		version		: '0.0.0.28',
		author		: 'Daryl Koopersmith',
		author_uri	: 'http://koopersmith.wordpress.com/',
		tags		: 'elastic'
	}
}



// Methods that extend jQuery declared here

$.fn.extend({

	// wait from the jQuery cookbook:  http://docs.jquery.com/Cookbook/wait 
	wait		: function(time, type) {
		time = time || 1000;
		type = type || "fx";
		return this.queue(type, function() {
			var self = this;
			setTimeout(function() {
				$(self).dequeue();
			}, time);
		});
	},
	gridWidth	: function() {
		return widthToCol(this.eq(0).width());
	},
	gridHeight	: function() {
		return heightToRow(this.eq(0).height());
	},
	gridTop		: function() {
		return pxToRow(this.eq(0).css('top').slice(0,-2));
	},
	gridLeft	: function() {
		return pxToCol(this.eq(0).css('left').slice(0,-2));
	},
	
	fixResizeWidth	: function() {
		return this.each(function(){
			var loc = $(this).data('location');
			
			var maxWidth = widthToCol($(this).resizable('option','maxWidth'));
			var maxFound = false;
			
			for ( var j = loc.left + loc.width; j < g.ui.array[0].length; j++) {
				for ( var i = loc.top; i < loc.top + loc.height; i++) {
					if ( g.ui.array[i][j] ) {
						maxWidth = j - loc.left;
						maxFound = true;
						break;
					}
				}
				if (maxFound) {
					$(this).resizable('option','maxWidth', colToWidth(maxWidth));
					break;
				}
			}
			
			// Reset to page_width
			if (!maxFound)
				$(this).resizable('option','maxWidth',g.bp.page_width);
				
		});
	},
	fixResizeHeight	: function() {
		return this.each(function(){
			var loc = $(this).data('location');
			
			var maxHeight = heightToRow($(this).resizable('option','maxHeight'));
			var maxFound = false;
			
			for ( var i = loc.top + loc.height; i < g.ui.array.length; i++) {
				for ( var j = loc.left; j < loc.left + loc.width; j++) {
					if ( g.ui.array[i][j] ) {
						maxHeight = i - loc.top;
						maxFound = true;
						break;
					}
				}
				if (maxFound) {
					$(this).resizable('option','maxHeight', rowToHeight(maxHeight));
					break;
				}
			}
			
			// Reset to page_height
			if (!maxFound)
				$(this).resizable('option','maxHeight',g.bp.page_height);
				
		});
	},
	
	// fillGrid and unfillGrid can be grouped since they use the same structure
	//		see the commented navigateMultiArray
	fillGrid		: function() {
		return this.each(function(){
			var loc = $(this).data('location');
			
			for (var i = loc.top - loc.mtop; i < loc.top + loc.height; i++) {
				for (var j = loc.left - loc.mleft; j < loc.left + loc.width + loc.mright; j++) {
					g.ui.array[i][j] = $(this);
				}
			}
		});
	},
	unfillGrid		: function() {
		return this.each(function(){
			var loc = $(this).data('location');
			
			for (var i = loc.top - loc.mtop; i < loc.top + loc.height; i++) {
				for (var j = loc.left - loc.mleft; j < loc.left + loc.width + loc.mright; j++) {
					g.ui.array[i][j] = '';
				}
			}
		});
	},
	removable		: function() {
		return this.each(function(){
			var self = $(this);
			$(this).append('<div class="ui-removable ui-icon ui-icon-close"></div>')
				.children('.ui-removable')
				.css({'z-index' : 1001})
				.click(function(){
					var cycleId = self.data('cycle');
						
					self.removeCycles()
						.unfillGrid()
						.remove();
				});
		});
	},
	label			: function() {
		return this.each(function(){
			var self = $(this);
			$(this).dblclick(function(){
				$(this).append('<div class="ui-dialog"><form action=""><select id="contentType"><option value="header">Header</option><option value="content">Main Content</option><option value="footer">Footer</option><option value="sidebar">Sidebar</option></select></form></div>').children('.ui-dialog').dialog({
					title : 'Type of Content',
					modal : true,
					buttons: {
						"Ok": function() {
							var o = self.data('location');
							var type = o.type;
							self.removeClass('type-'+type);
							
							type = $('#contentType').val();
							o.type = type;
							self.addClass('type-'+type);
							$(this).dialog("close");
						}
					},
					open: function(){
						$('#contentType').children('option[value='+ self.data('location').type +']').attr('selected', 'selected');
					},
					close: function(){
						$(this).remove();
					}
				});
			});
		});
	},
	containerHeight		: function() {
		var height = 0;
		console.log('container height');
		$(this).eq(0).children().each(function(){
			console.log('Child element', this, 'location', $(this).data('location'));
			height += $(this).data('location').height;
		});
		return height;
	},
	bpOutput		: function(items) {
		function bottom(n) { return n.top + n.height; }
		function right(n) { return n.left + n.width; }
		function left(n) { return n.left; }
	
		$.fn.extend({
			bpContinue : function(p,items) {
				return this.each(function(){
					$(this).bpAddChild(p).children('div:last').bpOutput(items);
				});
			},
			bpAddChild : function(p) {
				return this.each(function(){
					var last,
						item = $(this),
						// Extend o and p to make sure that #editor data isn't linked to #output data.
						o  = $(this).data('location'),
						oE = $.extend({},o),
						pE = $.extend({},p);
					
					console.log('adding',pE,'to',oE);
					
					
					if (oE.left + oE.width == pE.left + pE.width)
						last = ' last';
					else
						last = '';
						
					if(!o.container) {
						//o.container = true;
						console.log('making container');
						
						
						// Unlink this.data('location') from o
						$(this).data('location', $.extend({},o,{ container : true, type : 'group' }) );
						// Link o to the new child
						$(this).bpAddChild(o);
						// Reset o to this.data('location')
						o = $(this).data('location');
					}
					
					$(this).append('<div></div>')
						.children('div:last')
						.addClass('out span-'+pE.width+last)
						.data('location',pE);
					
					// Calculate the height of the container
					//    We can save directly to o, since at this point,
					//		this must be a container, and o will only be associated with this.
					o.height = bottom(pE) - o.top;
					
					var nspc = g.ui.cycle.bpOutput_nspc;
					$(this).findCycles(nspc);
					if(cycleGetNspc(nspc).length) {
						console.log('FOUND A CYCLE!   look at output:',$('#output').html());
						$(this).removeCycles(nspc);
						
						// Check if o was originally a container.
						if (!oE.container)
							$(this).children().remove();
						else
							$(this).children('div:last').remove();
						
						// Revert this.data('location') back to the original values
						$(this).data('location',oE);
						
						//console.log('o.container',o.container,'oE.container',oE.container);
						console.log('o',o,'$(this)',$(this).data('location'));
						p.rendered = false;
						console.log('removed stuff. output again:',$('#output').html());
					}	
					console.log('ADDED CHILD \t\t\t OUTPUT: ',$('#output').html());
				});
			}
		});
		
		/*
			TOP ROW
			
			topRow(o)
				returns the list of elements in area 'o' that do NOT have elements on top of them.
				
			
			There's a way to do this without using the grid.
				*  Check all elements for the 'top' attribute (in a given region)
				*  Select all elements inside the region with the lowest 'top' possible
					*  For each element, make sure it isn't wider than the area where it has
						the lowest top (this means that it has an element on top of it).
					*  Also, check if the element is completely inside the given region
		
		*/
		
		// This could be pulled into the $.fn.extend that comes before this.
		$.fn.topRow = function (top) {
			function topRowCore(o) {
				//console.log('topRowCore starting... o :',o);
				var items = [], space = [], prevSpace = false;
			
				function addSpace(width) {
					if (prevSpace) {
						space[space.length - 1].width += width;
					} else {
						space.push({
							width: width,
							left: j,
							// Add 1 to top since it will be input to topRow
							top: o.top + 1
						});
						prevSpace = true;
					}
				}
			
				for (var j = o.left; j < o.left + o.width; j++) {
					var item = g.ui.array[o.top][j];
				
					if (item) {
						var p = item.data('location');
					
					
						// if the item is larger than the area you're scanning, break
						// this statement is used in the invalid block as well--consolidate to fn?
						if (p.left < o.left || p.left + p.width > o.left + o.width)
							break;
					
						if (p.rendered) {
							addSpace(p.width);
						} else {
							console.log('topRow item found: ', item, 'location', p, 'this location', o);
							items.push( item );
							prevSpace = false;
						}
						j += p.width - 1;
					} else {
						addSpace(1);
					}
				}
			
				if ( ++o.top < g.ui.array.length ) {
					for (var i = 0; i < space.length; i++) {
						items = items.concat(topRowCore(space[i]));
					}
				}
				return items;
			}
		
			function compareBottom(a,b) {
				// sort by descending bottom order
				var c = a.data('location');
				var d = b.data('location');
				return bottom(d) - bottom(c);
			}
		
			function compareLeft(a,b) {
				// sort by ascending left order
				var c = a.data('location');
				var d = b.data('location');
				return c.left - d.left;
			}
			
			var o = $.extend({},$(this).data('location'));
			o.top = top || o.top;
			
			// CALCULATE INVALID
			var invalid = g.ui.array.length;
			var c = $(this);
			while (c.attr('id')!='output') {
				console.log('c',c,'c.parent.attr("id")',c.parent().attr('id'));
				var q = c.data('location');
				for (var i = o.top; i < g.ui.array.length; i++) {
					for (var j = q.left; j < q.left + q.width; j++) {
						var item = g.ui.array[i][j];
						if (item) {
							var p = item.data('location');
							if (p.left < q.left || p.left + p.width > q.left + q.width) {
								invalid = Math.min(i, invalid);
								break;
							}
						}
					}
				}
				c = c.parent();
			}
			
			console.log('INVALID HEIGHT',invalid);
		
			// RUN CORE
			var items = topRowCore(o);
		
			// REMOVE INVALID ELEMENTS
			// 		Sorts items by bottom, and removes all elements with bottom > invalid
			//		Continuously updates invalid
			if (invalid) {
				items.sort(compareBottom);
				var i = 0;
				console.log('items length', items.length);
				while( i < items.length && bottom(items[i].data('location')) > invalid) {
					console.log('found invalid item', items[i], 'invalid', invalid);
					invalid = Math.min(invalid, items[i].data('location').top);
					i++;
				}
				//
				//		IMPORTANT!!! WHEN YOU ADD/REMOVE THIS CONSOLE.LOG, UNCOMMENT THE FOLLOWING.
				//
				//console.log('removing items from array', items.splice(0,i));
				items.splice(0,i);			
			}
		
		
			// SORT ITEMS BY LEFT
			items.sort(compareLeft);
			return items;
		}
		
		
		
		// START BPOUTPUT
		//   Check for cycles
		if (cycleGetNspc().length > 0) {
			console.log('Please remove all cycles before generating output.');
			return;
		}
		
		return this.each(function(){
			var o = $.extend({},$(this).data('location'),{obj: $(this)});
			console.log('starting bpOutput',o);
			
			
			// If items is an array, items is the pre-calculated topRow
			// If items is a number, it is the row to start topRow at.
			if (!$.isArray(items)) {
				items = $(this).topRow(items);
			}
			
			
			console.log('topRow length', items.length);
			console.log('topRow',items);
			
			if (items.length) {
				var self = $(this);
				var tallest;
				
				$.each(items, function(){
					var p = this.data('location');
					if (!tallest || bottom(p) > bottom(tallest[0].data('location')))
						tallest = [this];
					else if (bottom(p) == bottom(tallest[0].data('location')))
						tallest.push(this);
				});
						
				for ( var j = 0; j < tallest.length; j++) {
					for( var i = 0; i < items.length; i++) {
						if (items[i] == tallest[j]) {
							// Extend p to avoid mixing #output and #editor data.
							//    o is already extended.
							
							var prevRight = o.left, p = tallest[j].data('location'), pE = $.extend({}, p);
							
							// Align items with the right side of the previous item
							if (tallest[j-1]) {
								prevRight = right(tallest[j-1].data('location'));
							}
							
							// Pass items left of tallest to container
							var containerWidth = pE.left - prevRight;
							if(containerWidth) {
								self.bpContinue({
									left : prevRight,
									width : containerWidth,
									top : pE.top,
									height : 0,
									container : true,
									type : 'group'
								}, items.splice(0,i));
							}
							// Delete tallest
							p.rendered = true;
							
							self.bpContinue(p);
							
							console.log('p.rendered',p.rendered);
							items.splice(0,1);
							
							// If last tallest, make right container
							if(!tallest[j+1] && right(pE) < right(o)) {
								self.bpContinue({
									left : right(pE),
									width : right(o) - right(pE),
									top : pE.top,
									height : 0,
									container : true,
									type : 'group'
								}, items);
							}
						}
					}
				}
				
				console.log('bottom tallest', bottom(tallest[0].data('location')), 'tallest', tallest[0]);
				$(this).bpOutput(bottom(tallest[0].data('location')));
				

					// Set margin-top
					//p.mtop = p.top - o.top;
									
					// Create containers, but delete them and substitute with margins if empty			
					//self.bpContinue(p);


			
			}
			
			//
			//
			//
			//  DEAL WITH EMPTY
			//
			//
			$(this).filter(function(){
				if($(this).is(':empty') && o.container)
					console.log('REMOVING EMPTY CONTAINER\t\tOUTPUT:',$('#output').html());
				return $(this).is(':empty') && o.container;
			}).remove();
			
			
			console.log('BPOUTPUT COMPLETE', this,'\t\tOUTPUT:',$('#output').html());
			
			//
			//
			//
			//	ADD CHECK FOR ONE-ITEM CONTAINERS
			//
			//
			
			
			
			
			// Check sides
			/*function checkSideHelper(j) {
				for (var i = p.top - p.mtop; i < height(p); i++) {
					if(g.ui.array[i][j])
						return true;
				}
				return false;
			}
			
			for (var j = 0; j < p.left; j++) {
				if(checkSideHelper(p.left - j))
					break;
			}*/

			
			// get item at top, left
			/*var item = findItem(o);
			if (!item)
				return;
				
			var p = item.data('location');
			*/
			// If an item goes outside the area that we're scanning, stop!
			/*if (p.left < o.left || p.left + p.width > o.left + o.width)
				return;
			
			
			tallest = max(tallest, p.height);
			*/
			
			
			// If there is whitespace...
			
			//
			//
			//
			//  NOTE: ACCOUNT FOR THE TALLEST DIV FIRST
			//			KEEP TABS ON THE CURRENT LEFT POSITION
			//			WHEN CALCULATING WHITESPACE, KNOW WHERE THE NEAREST LEFT ELEMENT IS
			//			THIS DOES NOT ACCURATELY DETECT WHITESPACE
			//
			//
			//
			/*if (p.left > o.left) {
				var space = p.left - o.left;
				
				// create new div with space
				$(this).bpContinue({});
				$(this).append('<div class="span-'+space+'"></div>')
					.children('div:last')
					.data('location',{
						width : space,
						top : p.top,
						left : o.left
					}).bpOutput();
				
			}*/
			
			
			
			
		});
	},
	findCycles			: function(nspc) {
		nspc = cycleGetNspc(nspc, true);
		$.fn.selector = nspc.selector;
		
		//console.log('NAMESPACE',nspc.name);
		
		function t(a) { return a.top * 2; }
		function l(a) { return a.left * 2; }
		function b(a) { return (a.top + a.height)*2 - 1; }
		function r(a) { return (a.left + a.width)*2 - 1; }
		function lr(a,c) { return (l(a) < r(c)) && (l(c) < r(a)); };
		function tb(a,c) { return (t(a) < b(c)) && (t(c) < b(a)); };
		
		function findCycle(items, primary) {
			var o = $.map(items, function(a) {return a.data('location'); });
			
			//add a blank element to the start of the array for number convention
			o.unshift('');
			
			
			// there are 6 tests total. for each primary element,
			// associate the three tests that AREN'T the primary tests
			var t12,t13,t14,t23,t24,t34,tests, cycle = true;
			
			
			t12 = tb(o[1],o[2]);
			t13 = lr(o[1],o[3]);
			t14 = lr(o[1],o[4]);
			t23 = lr(o[2],o[3]);
			t24 = tb(o[2],o[4]);
			t34 = tb(o[3],o[4]);
			
			
			if ('e1' == primary) {
				tests = [t23, t24, t34];
			} else { // 'e2' == primary
				tests = [t13, t14, t34];
			}
			
			for (var i = 0; i < tests.length; i++)
				cycle = cycle && tests[i];
			
			if (cycle) {
				console.log('CYCLE FOUND', 'primary', primary, 'items', items);
				return items;
			} else
				return false;
		}
		
		function arrayToCycle (a) {
			var items = [], cycles = [];
			
			$.extend({
				cycleEach : function(arr, callback) {
					$.each(arr, function() {
						// make sure that an item isn't repeated
						for(var i = 0; i < items.length; i++) {
							if (items[i].attr('id') == $(this).attr('id'))
								return true;
						}
						items.push($(this));
						callback();
						items.pop($(this));
					});
				}
			});
			
			
			$.cycleEach(a[0], function() {
				$.cycleEach(a[1], function() {
					$.cycleEach(a[2], function() {
						$.cycleEach(a[3], function() {
							var c = findCycle(items, a[4]);
							// must clone/extend c, since c is a reference to items
							if (c) cycles.push($.extend({},c));
						});
					});
				});
			});
			return cycles;
		}
		
		return this.each(function(){
			$(this).removeCycles(nspc);
			
			var o = $(this).data('location');
			var top = [], left = [], cycles = [];
			
			$(this).selector().not(this).each(function(){
				var p = $(this).data('location');
				if ( tb(o,p) ) top.push($(this));
				if ( lr(o,p) ) left.push($(this));
			});
			
			//console.log('arrays', 'top', top, 'left', left);
			
			var potentialCycles = [
				[$(this), top, left, left, 'e1'],
				[top, $(this), left, top, 'e2']
			];

			$.each(potentialCycles, function() {
				$.merge(cycles, arrayToCycle(this));
			});
			
			//console.log(cycles.length,'cycles found', cycles);
			
			$.each(cycles, function() {
				$.each(this, function() {
					this.addClass('cycle cycle-'+ nspc.name + nspc.id);
					var cycleId = this.data('cycle');
					if (cycleId) {
						cycleId.push( nspc.name + nspc.id);
						this.data('cycle',cycleId);
					} else {
						this.data('cycle',[ nspc.name + nspc.id]);
					}
				});				
				nspc[nspc.name + nspc.id] = this;
				nspc.length++;
				nspc.id++;
			});
			
			//console.log('cycle check complete');
		});
	},
	removeCycles		: function(nspc) {
		nspc = cycleGetNspc(nspc);
		return this.each(function(){
			var cycleId = $(this).data('cycle');
			if(cycleId && nspc) {
				// make sure to extend cycleId, since we will be modifying it inside
				$.each($.extend({},cycleId), function(){
					var cId = this;
					$('.cycle-'+this).each(function(){
						var c = $(this).data('cycle');
						if (c.length > 1) {
							for(var i = 0; i < c.length; i++) {
								if (c[i] == cId) {
									c.splice(i,1);
								}
							}
						} else {
							$(this).removeData('cycle')
								.removeClass('cycle');
						}
					}).removeClass('cycle-'+this);
					nspc[this] = '';
					nspc.length--;
					
					//console.log('length',nspc.length, 'this', this);
				});
			}
		});
	},
	inspectable			: function(options) {
		var defaults = {
			event : 'click'
		}
		
		var settings = $.extend( true, {}, defaults, options );
		
		return this.each(function(){
			$(this).bind(settings.event, function() {
				// Replace everything in the selected array with this.
				
				$('#inspector').trigger('inspector-before-update');
				g.ir.selected = [ $(this) ];
				$('#inspector').trigger('inspector-update');
				
				$('#tabs-inspector').tabs('select','#ir-module');
			});		
		});
	}

});

function cycleGetNspc(nspc, set) {
	set = set || false;
	nspc = nspc || g.ui.cycle.default_nspc;
	if (typeof nspc == 'string')
		nspc = { name : nspc };
	
	if(!g.ui.cycle[nspc.name]) {
		if (set)
			g.ui.cycle[nspc.name] = $.extend({}, g.ui.cycle.init_nspc, nspc);
		else
			return false;
	}
	return g.ui.cycle[nspc.name];
}

/*function navigateMultiArray (a, b, c, d, f) {
	for (var i = a; i < b; i++) {
		for (var j = c; j < d; j++) {
			f(i,j);
		}
	}
}*/


$(document).ready(function() {
	console.log('document ready');
	
	parseInput();
	initDialogs();
	initInspector();
	initResizable();
	initUI();
	initBlueprint();
});

function parseInput() {
	// Replace encoded quotes
	$.each(input, function(key, val){
		input[key] = val.replace(/&quot;/g,'"');
	});
	
	input.themes = JSON.parse(input.themes);
}

function initDialogs() {
	
	loadDialogInspector();
	loadDialogOverlay();
	//loadDialogInstructions();
	//loadGenerateOutput();
	
	
	
	function loadDialogInspector() {
		$('#dialog-inspector').dialog({
			autoOpen : true,
			title : 'Elastic Editor',
			modal : false,
			width : 400,
			closeOnEscape : false,
			resizable : false,
			minHeight : false,
			zIndex: 10000,
			open: function() {
				var ir = $(this).parent();
					
				ir.css({ 'top' : ir.height() * -1 }).wait(500).queue(function(){
					var dist = ( $(window).height() / 2 ) - ( ir.height() / 2 );
					
					$(this).fadeTo(0, 0).css({'top': (dist - 25) })
						.animate({ top : dist+'px', opacity : 1 }, 500)
						.dequeue();
				})
					//
			}
		})
		.parent().fadeTo(0, 1).hover(
			function(){ $(this).fadeTo(150, 1); },
			function(){
				var dialog = $(this);
				if ( ! ( dialog.hasClass('ui-dialog-dragging') || dialog.hasClass('ui-dialog-resizing') ) )
					dialog.fadeTo(200, 1);
			}
		).attr('id','inspector')
		.append('<div id="inspector-loading"><div class="ui-corner-all">Working...</div></div>');
		
		$('#inspector-loading').hide();
		
		// Hide dialog tab navigation. Tabs are controlled by outside elements.
		$('#tabs-inspector').tabs().children('.ui-tabs-nav').hide();
		$('#ir-module').tabs();
	}
	
	function loadDialogOverlay() {
		$('#dialog-overlay').dialog({
			autoOpen : false,
			closeOnEscape : false,
			draggable : false,
			modal : true,
			resizable : false,
			zIndex : 99999,
			open : function() {
				$('body').addClass('dialog-overlay-active'); // CSS makes overlay transparent
			},
			close : function() {
				$('body').removeClass('dialog-overlay-active');
			}
		}).parent().addClass('hide'); // Hides dialog--we only care about the overlay
	}
	
	function loadDialogInstructions() {
		$('#dialog-instructions').dialog({
			autoOpen : false,
			title : 'Instructions',
			modal : false,
			width : 500
		});
		$('#tb-instructions').click(function(){
			$('#dialog-instructions').dialog('open');
		});
	}
	
	function loadDialogChangeGrid() {
		$('#dialog-changegrid').keyup(function(){
			var cc = $('#column_count').val() * 1,
				cw = $('#column_width').val() * 1,
				gw = $('#gutter_width').val() * 1,
				pw = cc * ( cw + gw );
			$('#page_width').val( pw );
		});
		$('#dialog-changegrid').dialog({
			title : 'Choose Your Grid',
			modal : true,
			buttons: {
				"Ok": function() {
					var cc = $('#column_count').val() * 1,
						cw = $('#column_width').val() * 1,
						gw = $('#gutter_width').val() * 1;

					if(cc == 0 && cw == 0 && gw == 0)
						fixBlueprintCSS(24, 30, 10);
					else
						fixBlueprintCSS(cc, cw, gw);

					$(this).dialog("close");
				},
				"Default": function() {
					$('#column_count').val(24);
					$('#column_width').val(30);
					$('#gutter_width').val(10);
					$('#dialog-changegrid').keyup();
				}
			}
		});
	}
	
	
	function loadGenerateOutput() {
		$('#dialog-generateoutput').dialog({
			autoOpen : false,
			title : 'WARNING',
			modal : true,
			buttons: {
				"Ok": function() {
					generateOutputButton();
					$(this).dialog("close");
					if ($('#generate-output-warn-again').is(':checked'))
						$(this).remove();
				},
				"Cancel": function() {
					$(this).dialog("close");
				}
			}
		});
		
		$('#tb-generate-output').click(function(){
			console.log('generating output...');

			if( cycleGetNspc().length > 0 ) {
				$(this).html('Get rid of the <strong>red</strong> and try again!');
			} else if ( ! $('.e').length ) {
				$(this).html('<strong>Add something</strong> and try again!');
			} else {
				generateOutputDialog();
			}

			$(this).wait(2000).queue(function(){
				$(this).html('Customize <em>Elastic</em>');
				$(this).dequeue();
			});
		});
	}
	
	function generateOutputDialog() {
		//if($('#dialog-generate-output').is('div'))
		//	$('#dialog-generate-output').dialog('open');
		//else
			generateOutputButton();
	};
	
	function generateOutputButton() {
		var go = $('#tb-generate-output');
		go.html('Updating <em>Elastic</em>...');
		generateOutput();
		go.html('<em>Elastic</em> Updated!');
		go.wait(2000).queue(function(){
			$(this).html('Customize <em>Elastic</em>');
			$(this).dequeue();
		});
	}
}

/* ---------------------------------------------------


		I  N  S  P  E  C  T  O  R     F  U  N  C  T  I  O  N  S

	ADD COLUMN
		* Check if point is taken
		* Adds column at point
	
	
--------------------------------------------------- */

function initInspector() {
	irLoad();
	irSave();
	irNew();
	irInstructions();
	irLabelForm();
}

function irLoad() {
	var select = $('#ir-load-select'),
		options = '';
	
	$.each(input.themes, function(){
		if ( this.compatible )
			options += '<option value="' + this.name + '">' + this.name + '</option>';
	});
	
	select.html(options);
	$('#ir-load-button-load').click(function(){
		g.ir.fn.canvasLoadBegin();
		var name = select.val();
		$.post('admin-ajax.php', {
			action : 'elastic_load_state',
			name : name
		}, function(data) {
			data = JSON.parse(data);
			loadState( data );
			
			$.each(input.themes, function(){
				if( name === this.name ) {
					g.theme = $.extend( {}, g.theme, this );
					return false;
				}
			});
			
			g.ir.fn.canvasLoadEnd();
		});
	});
	
	$('#ir-load-button-new').click(function(){
		$('#tabs-inspector').tabs('select','#ir-new');
	});
}

function irSave() {
	var save = $('#ir-save-input'),
		paths = [];
	
	$.each(input.themes, function(){
		paths.push( this.path );
	});
	
	console.log('paths',paths);
	
	g.ir.fn.bindTabSelect( '#ir-save', function(){
		save.val( g.theme.name );
	});
	
	save.qtip( qTipSettings('name-taken') )
		.keyup(function(){
			var name = $(this).val();
			
			if( nameExists( name ) ) { // Let the user know if the name is taken
				$(this).qtip('show');
			} else {
				$(this).qtip('hide');
			}
		});
	
	$('#ir-save-button').click(function(){
		g.ir.fn.canvasLoadBegin();
		
		var name = save.val(),
			options = {},
			path;
		
		if ( name === g.theme.name ) {
			path = g.theme.path;
		} else {
			name = resolveDuplicates( name, nameExists );
			path = nameToPath( name );
		}
		
		options.name = sanitizeName( name ).replace(/[/][^/]+$/g, ''); // Sanitize name and remove path suffix
		options.path = path;
		generateOutput( options, g.ir.fn.canvasLoadEnd );
	});
	
	$('#tb-save').click(function(){
		$('#tabs-inspector').tabs('select','#ir-save');
	});
	
	function nameExists( name ) { return ( $.inArray( nameToPath( name ), paths ) !== -1 ) }
	
	function nameToPath( name ) {
		var path = name.replace(/[ _]/g, '-')
			.replace(/[^a-zA-Z0-9\-/]/g,'')
			.replace(/^[\-/\d]+/g, '') // remove leading hyphens, slashes, and digits
			.toLowerCase();
		
		
		var match = path.match(/.[/][^/]+$/); // check for a path suffix
		if( match )
			path = match.toString().slice(2).toString();
			
		path = path.replace(/[/]/g, '-'); // replace remaining slashes

		console.log('name',name, 'path',path);
		
		return path;
	}
}

function irNew() {
	$('#ir-new-form table').keyup(function(){
		var a = irNewGrid();
		
		console.log('a',a);
		
		$('#ir-new-pw').val( a.pw() );
	});
	
	$('#ir-new-select').change(function(){
		var grid = $(this).val().split(',');
		$('#ir-new-cc').val( grid[0] );
		$('#ir-new-cw').val( grid[1] );
		$('#ir-new-gw').val( grid[2] );
		$('#ir-new-form table').keyup();
	});
	
	$('#ir-new-button').click(function(){
		var a = irNewGrid();

		if(a.cc == 0 && a.cw == 0 && a.gw == 0)
			fixBlueprintCSS(24, 30, 10);
		else
			fixBlueprintCSS(a.cc, a.cw, a.gw);
	});
	
	function irNewGrid() {
		return {
			cc : $('#ir-new-cc').val() * 1,
			cw : $('#ir-new-cw').val() * 1,
			gw : $('#ir-new-gw').val() * 1,
			pw : function() { return this.cc * ( this.cw + this.gw ) - this.gw }
		}
	}
}

function irInstructions() {
	$("#tb-instructions").click(function(){
		$('#tabs-inspector').tabs('select','#ir-instructions');
		
		// Center the inspector
		var ir = $('#inspector'),
			dy = ( $(window).height() / 2 ) - ( ir.height() / 2 ),
			dx = ( $(window).width() / 2 ) - ( ir.width() / 2 );
			
		ir.animate({ top : dy+'px', left : dx+'px' });
	});
}

function irLabelForm() {
	var form = {
			name : $('#mod-label-name'),
			type : $('#mod-label-type'),
			id   : $('#mod-label-id'),
			name_span : $('#mod-label-name-span')
		},
		labels = g.ir.labels,
		defaultName = 'Sidebar 1';
	
	$('#inspector').bind('inspector-before-update', function(){
		if( getSelectedObj() ) {
			if ( ! isLimited( getSelectedLoc().type ) ) { // Check if type is named
				form.name.triggerHandler('focus'); // Trigger focus/blur in case input is still selected
				form.name.triggerHandler('blur');
			}
		}
	});
	
	$('#inspector').bind('inspector-update', function() {
		var o = getSelectedLoc();
		
		form.type.children('option[value='+ o.type +']')
			.attr('selected', 'selected');
				
		updateName();
	});
	
	form.type.change(function(){
		var item = getSelectedObj(),
			o = getSelectedLoc();
			
		item.removeClass('type-'+o.type);
		o.type = form.type.val();
		item.addClass('type-'+o.type);
		
		updateName();
	});
	
	form.name.focus( function () {
		var o = getSelectedLoc();
		
		if (o.name) {
			labels.remove( o.name ); // Unset name while user is typing
		}
	})
	.blur( function() {
		var o = getSelectedLoc(),
			name = sanitizeName( $(this).val() );
		
		$(this).qtip('hide'); // Hide tooltip. Name fixed below.
		
		
		name = resolveDuplicates( name, labels.has );
		
		o.name = name;
		labels.add( o.name );
		form.name.val( o.name );
	})
	.qtip( qTipSettings('name-taken') )
	.keyup(function(){
		var o = getSelectedLoc(),
			name = $(this).val();
		
		updateIdInput( name );
		
		if( labels.has( name ) ) { // Let the user know if the name is taken
			$(this).qtip('show');
		} else {
			$(this).qtip('hide');
		}
	});
	
	function updateIdInput( id ) {
		var o = getSelectedLoc();
		
		id = nameToId( id );
		form.id.val('#'+id);
	}
	
	function updateName() {
		var o = getSelectedLoc();
		
		// TODO: Change this once type limits are introduced
		if ( ! isLimited(o.type) ) { // Check if type can be named
			var name = o.name,
				blur = false;
			
			if( ! o.name || isLimited( o.name ) ) { // If name is undefined or the name of an unnamed type.
				name = defaultName;
				blur = true;
			}
			
			form.name_span.show();
			form.name.val(name);
			
			if( blur ) {
				form.name.triggerHandler('blur');
			}
			form.name.triggerHandler('focus'); // Trigger focus if input is already selected
		} else {
			form.name_span.hide();
			o.name = o.type;
			form.name.val(o.name);
		}	
		
		updateIdInput( o.name );
	}
	
	function isLimited( type ) {
		var limited = ["content", "header", "footer"],
			index = $.inArray( type, limited );
		return ( -1 !== index );
	}
	
	function getSelectedObj() {
		return g.ir.selected[0];
	}
	
	function getSelectedLoc() {
		return g.ir.selected[0].data('location');
	}
}


/* ---------------------------------------------------


		U  I     F  U  N  C  T  I  O  N  S

	ADD COLUMN
		* Check if point is taken
		* Adds column at point
	
	
--------------------------------------------------- */ 

function initUI() {
	// Create multi-demensional array
	g.ui.array = multiArray( g.bp.row_count, g.bp.column_count );
	
	// Init button logic
	buttonLogic();

	// Add elements when the editor is clicked
	$('#editor').mousedown(function(e){
		var target = $(e.target);
		if(e.target == this || target.hasClass('grid_column') || target.attr('id') == 'dynamic_grid') {
			var offset = $('#editor').offset();
			var xFloat = pxToCol(e.pageX - offset.left);
			var yFloat = pxToRow(e.pageY - offset.top);
			var col = Math.floor(xFloat);
			var row = Math.floor(yFloat);
			//console.log('xFloat '+xFloat+' x '+ row +'    yFloat '+yFloat+'  y '+ col);
			uiAddColumn(row, col);
		}
	});
}


function uiAddColumn(i, j) {
	var o, defaults = {
		height : 1,
		width  : 1,
		mtop   : 0,
		mleft  : 0,
		mright : 0,
		container : false,
		type : 'sidebar'
	};
	
	
	// Either take a location object, or top/left coords
	if( typeof i == 'object' ) {
		o = $.extend({}, defaults, i);
	} else {
		o = $.extend({}, defaults, { top : i, left : j } );
	}
	
	if(!g.ui.array[o.top][o.left]) {
		g.ui.index++;
		$('#editor').append('<div id="e'+g.ui.index+'" class="e resizable">'+g.ui.index+'</div>');
		
		
		var item = $('#e'+g.ui.index);
		// Specify top/left and make resizable. Use classes
		item.data('location', o)
			.addClass('top-'+ o.top +' left-'+ o.left +' span-'+ o.width +' height-'+ o.height +' type-'+ o.type +' ui-corner-all')
			.fillGrid()
			.resizable(getResizableDefaults())
			.draggable(getDraggableDefaults())
			.droppable(getDroppableDefaults())
			.removable()
			.inspectable().click();
		
		
		//item.children('.ui-resizable-se').trigger('mousedown');
	}
}
/* ---------------------------------------------------
	BUTTON LOGIC
		jQuery UI button logic
		Credit: http://www.filamentgroup.com/lab/styling_buttons_and_toolbars_with_the_jquery_ui_css_framework/
--------------------------------------------------- */
function buttonLogic() {
	$(".el-button:not(.ui-state-disabled)")
	.hover(
		function(){
			$(this).addClass("ui-state-hover");
		},
		function(){
			$(this).removeClass("ui-state-hover");
		}
	)
	.mousedown(function(){
		$(this).parents('.el-buttonset-single:first').find(".el-button.ui-state-active").removeClass("ui-state-active");
		if( $(this).is('.ui-state-active.el-button-toggleable, .el-buttonset-multi .ui-state-active') ){ $(this).removeClass("ui-state-active"); }
		else { $(this).addClass("ui-state-active"); }
	})
	.mouseup(function(){
		if(! $(this).is('.el-button-toggleable, .el-buttonset-single .el-button, .el-buttonset-multi .el-button') ){
			$(this).removeClass("ui-state-active");
		}
	});
}
/* ---------------------------------------------------
	TOOLTIPS
		jQuery qTip plugin
--------------------------------------------------- */

function qTipSettings( name ) {
	var options, defaults = {
		show: false,
		hide: false,
		api: {
			onShow: function() {
				$( this.elements.tooltip ).css({ zIndex : 20000 });
			}
		}
	}
	
	if ( 'name-taken' === name ) {
		options = {
			content: { text: 'Name taken.' },
			position: {
				corner : {
         			target: 'rightMiddle',
	         		tooltip: 'leftMiddle'
				}
			},
			style: {
				name: 'red',
				tip: {
					corner: 'leftMiddle',
					size: {
						x: 5,
						y: 5
					}
				},
				border: {
					width: 1,
					radius: 3
				}
			}
		};
	} else if ('loading' === name) {
		options = {
			content: { text: 'Working...' },
			position: { corner : 'center' },
			style: {
				name: 'dark',
				border: {
					width: 4,
					radius: 6
				}
			}
		}
	}
	
	return $.extend( true, {}, defaults, options );
}

function qTipModuleDefaults( item ) {
	return {
		//content: { text: '<div id="'+ item.attr('id') +'-qtip">'+ o.type +'</div>' },
		
		// Add autocomplete off to prevent internal firefox bug: http://blog.taragana.com/index.php/archive/solving-permission-denied-to-get-property-htmldivelementnodetype-when-calling-method-nsidomeventlistenerhandleevent-nsresult-0x8057001e-ns_error_xpc_js_threw_string-location-data-no/
		content: { text: '<div><form class="qtip-label" autocomplete="off"><select autocomplete="off"><option value="header">header</option><option value="content">content</option><option value="footer">footer</option><option value="sidebar">sidebar</option></select><input type="text" size="11" autocomplete="off"></input></form><div>'},
		position: {
			corner : {
     			target: 'rightMiddle',
         		tooltip: 'leftMiddle'
			}
		},
		show: {
			when: { event: 'click' },
			delay: 10,
			effect: { length: 100 }
		},
		hide: {
			when: { event: 'unfocus'},
			delay: 50,
			effect: { length: 200 },
			fixed: true // Make it fixed so it can be hovered over
		},
		style: {
			name: 'dark',
			border: {
				width: 4,
				radius: 2
			},
			tip: {
				corner: 'leftMiddle',
				size: {
					x: 5,
					y: 5
				}
			}
		},
		api: {
			onRender: function() {
				var o = item.data('location'),
					api = item.qtip('api');
				api.elements.content.find('option[value='+ o.type +']').attr('selected', 'selected').end().find('input').val((o.name)?o.name:'name');
				api.elements.content.find('select').change(function(){
					item.removeClass('type-'+o.type);
					o.type = api.elements.content.find('select').val();
					item.addClass('type-'+o.type);
					
				}).end().find('input').focus( function () {
					if (o.name) {
						var arr = g.label[o.type];
						arr.splice( $.inArray(o.name, arr), 1 );
					}
				}).keyup(function(){
					var input = api.elements.content.find('input'),
						name = input.val();
					console.log('name',name,'array',g.label[o.type],'type',o.type,'g.label',g.label);
					if( -1 !== $.inArray(name, g.label[o.type]) ) {
						input.qtip('show');
					} else {
						input.qtip('hide');
					}
				}).qtip({
					content: { text: 'Name taken.' },
					position: {
						corner : {
		         			target: 'rightMiddle',
			         		tooltip: 'leftMiddle'
						}
					},
					show: {
						when : {
							event : false
						}
					},
					style: {
						name: 'red',
						tip: {
							corner: 'leftMiddle',
							size: {
								x: 5,
								y: 5
							}
						},
						border: {
							width: 1,
							radius: 3
						}
					}
				});
				/*api.elements.wrapper.click(function(){
					//console.log('log');
					if ( ! api.elements.content.is(':has(input)')) {
						//api.updateContent('<div class="qtip-label"><select><option value="header">header</option><option value="content">content</option><option value="footer">footer</option><option value="sidebar">sidebar</option></select><input type="text" size="11"></input></div>');
						api.elements.content.find('option[value='+ o.type +']').attr('selected', 'selected').end().find('input').val((o.name)?o.name:'name');
					}
				});*/
			},
			onHide: function() {
				var o = item.data('location'),
					api = item.qtip('api'),
					input = api.elements.content.find('input'),
					name = input.val();
					
				input.qtip('hide');
				if ( name !== 'name' && -1 === $.inArray(name, g.label[o.type]) ) {
					o.name = name;
					if( name !== '')
						g.label[o.type].push(name);
				} else {
					input.val((o.name)?o.name:'name');
				}
			}
		}
	}
}

/* ---------------------------------------------------
	RESIZABLE
		Makes columns resizable
--------------------------------------------------- */ 


function initResizable() {
	$(".resizable").resizable(getResizableDefaults());
}

// Maybe just override defaults? $.extend($.fn.resizable.defaults, newDefaults);
function getResizableDefaults() {
	return {
		containment: $('#editor'),
		grid: [colUnit(), rowUnit()],
		maxWidth: g.bp.page_width,
		maxHeight: g.bp.page_height,
		handles: 'e, s, se',
		start: function() {
			var loc = $(this).data('location');
			
			$(this).fixResizeWidth().fixResizeHeight().unfillGrid();
			
			
			// These must be last lines--height and width are removed
			$(this).removeClass('span-'+loc.width)
				.removeClass('height-'+loc.height);
		},
		resize: function() {
			var loc = $(this).data('location');
			//console.log('resize triggered');
			
			// GRID BASED RESIZE
				// Only trigger resize function if the element is actually resized
				// Originally, this is triggered with any mouse movement
			var newWidth = $(this).gridWidth();
			var oldWidth = loc.width;
			var newHeight = $(this).gridHeight();
			var oldHeight = loc.height;

				
			// Width-based
			if(newWidth != oldWidth) {
				// Update value -- must be first line
				loc.width = newWidth;
				
				$(this).fixResizeHeight();
			}
			
			// Height-based
			if(newHeight != oldHeight) {
				// Update value -- must be first line
				loc.height = newHeight;
				
				$(this).fixResizeWidth();
			}
			
			
			// Inverse resize functionality
			
		},
		stop: function() {
			var loc = $(this).data('location');
			
			$(this).addClass('span-'+loc.width+' height-'+loc.height);
			
			// Remove inline properties
			$(this).css({top: '', left : '', width : '', height : ''});
			
			$(this).fillGrid();
			$(this).findCycles();
		}
	}
}

/* ---------------------------------------------------
	DRAGGABLE
		Makes columns draggable
--------------------------------------------------- */ 

function getDraggableDefaults() {
	return {
		containment: $('#editor'),
		//cursor: 'crosshair',
		grid: [colUnit(), rowUnit()],
		revert: 'valid',
		revertDuration: 150,
		start: function() {
			var loc = $(this).data('location');
			
			$(this).data('temp',{
				origLeft : loc.left,
				origTop  : loc.top,
				drop     : false
			});
			
			$(this).unfillGrid();
			
			// These must be last lines--height and width are removed
			$(this).removeClass('left-'+loc.left)
				.removeClass('top-'+loc.top);
			
			//$(this).css({'left': $(this).css('left'), 'top' : $(this).css('top') });
		},
		drag: function() {
			var loc = $(this).data('location');
			var temp = $(this).data('temp');
			//$(this).css({'left': '0px'});
			//console.log('drag triggered', $(this).css('left'));
			
			// GRID BASED DRAG
				// Only trigger drag function if the element has moved
				// Originally, this is triggered with any mouse movement
			var newLeft = $(this).gridLeft();
			var oldLeft = loc.left;
			var newTop = $(this).gridTop();
			var oldTop = loc.top;		

				
			// Width-based
			if(newLeft != oldLeft) {
				loc.left = newLeft;
				
			}
			
			// Height-based
			if(newTop != oldTop) {
				loc.top = newTop;
				
			}
			
		},
		stop: function() {
			var loc = $(this).data('location');
			var temp = $(this).data('temp');
			
			//console.log('on droppable',temp.drop);
			
			if(temp.drop) {
				// wait -- try not to cut off the revert animation
				$(this).wait($(this).draggable('option','revertDuration')+100, 'wait');
				
				// Add left and top classes
				// reset original position (revert doesn't always leave the div in the right place)
				loc.left = temp.origLeft;
				loc.top = temp.origTop;
			} else {
				// Sometimes drag doesn't trigger, so let's update the left/top to be sure
				//			Bug--drag doesn't trigger if user moves element box one grid space very quickly
				loc.left = $(this).gridLeft();
				loc.top = $(this).gridTop();
			}
			// Add left and top classes			
			$(this).addClass('left-'+loc.left+' top-'+loc.top);
			
			// Remove inline properties & temp data
			$(this).css({top: '', left : '', width : '', height : ''})
				.removeData('temp');
			
			$(this).fillGrid();
			$(this).findCycles();
		}
	}
}

/* ---------------------------------------------------
	DROPPABLE
		Makes columns droppable
--------------------------------------------------- */ 

function getDroppableDefaults() {
	return {
		tolerance: 'touch',
		drop: function(event, ui) {
			var data = ui.draggable.data('temp')
			if ( data )
				data.drop = true;
		}
	};
}

/* ---------------------------------------------------


		G  E  N  E  R  A  T  E     O  U  T  P  U  T



	GENERATE OUTPUT
		Use the array to convert divs to a relative layout.
		
					
--------------------------------------------------- */

function generateOutput( options, callback ) {
	var settings = $.extend( true, {}, g.theme, options );
	
	callback = callback || function(){};
	
	
	$('#outputWrapper').remove();
	$('body').append('<div id="outputWrapper"><div id="output" class="container"></div></div>');
	$('.e').filter(function(){ return $(this).data('location').rendered; }).css({ background : 'blue'});
	cycleGetNspc(g.ui.cycle.bpOutput_nspc, true);
	$('#output').data('location',{
		width : g.ui.array[0].length,
		top : 0,
		left : 0,
		height : 0,
		container : true,
		type : 'group'
	}).addClass('out').bpOutput();
	
	console.log('OUTPUT GENERATED.');
	
	// Check for items missed
	var missed = $('.e').filter(function(){
		return !$(this).data('location').rendered;
	});
	
	
	console.log('Items missed: ', missed);
	
	// Reset rendered
	$('.e').each( function() {
		$(this).data('location').rendered = false;
	});
	
	
	// Add ids & classes to output
	var count = {};
	var css = '';
	
	$('.out').each(function(){
		// If a div has children, it's a group!
		// $.extend( $(this).filter(':parent').data('location') , { type : 'group'});
		
		var type = $(this).data('location').type;
		var name = $(this).data('location').name;
			
		if (!name || 'group' === type ) {
			if( ! count[type] )
				count[type] = 1;
			name = 'group-' + count[type];
			
			$.extend( $(this).data('location') , { name : name });
			
			count[type]++;
		}
		
		name = nameToId( name );
		
		$(this).addClass(type);
		
		$(this).addClass('module');
		$(this).attr('id', name);
		
		css += '#'+ name + ' {';
		css += 'width : '+$(this).width()+'px;'
		css += 'margin-right : '+$(this).css('margin-right')+';';
		css += '}\n';
		
	});
	
	$('.out').removeClass('out');
	
	console.log('OUTPUT FORMATTED: ',$('#outputWrapper').html());
	
	var layout = outputToJSON();
	console.log('JSON OUTPUT: ',layout);
	
	$.post('admin-ajax.php', {
		action : 'process_theme',
		settings : JSON.stringify(settings),
		structure : css,
		layout : JSON.stringify(layout),
		state : JSON.stringify(saveState())
	}, function(){
		callback();
	});
	

}

function outputToJSON() {
	var json = {};
	outputRecurse( $('#outputWrapper > div'), json );
	return json;
	
	// outputRecurse(jQuery, JSON)
	function outputRecurse(obj, ptr) {
		var o = obj.data('location');
		
		// Might be a problem if name is being passed by reference. Probably not, since ptr won't be changed.
		if(o.name)
			ptr.name = nameToId( o.name );
		if(o.type)
			ptr.type = o.type;

		if (obj.is(':parent')) {
			ptr.children = [];
			var arr = ptr.children;
			
			obj.children().each(function(){
				arr.push({});
				outputRecurse($(this), arr[ arr.length - 1 ] );
			});
		}
	}
}

function saveState() {
	var json = {};
	json.layout = [];
	json.bp = $.extend({}, g.bp);
	
	$('.e').each(function(){
		json.layout.push($(this).data('location'));
	});
	
	return json;
}


function loadState( state ) { // Expects an object
	$.extend(g.bp, state.bp);
	fixBlueprintCSS(g.bp.column_count, g.bp.column_width, g.bp.gutter_width);
	
	for (var i = 0; i < state.layout.length; i++) {
		uiAddColumn(state.layout[i]);
	}
}



/* ---------------------------------------------------


		B  L  U  E  P  R  I  N  T



	FIX BLUEPRINT CSS
		Dynamically recalculates the grids.css portion of blueprint.
		
					
--------------------------------------------------- */ 

function initBlueprint() {
	
	
	// Add the style block
	$('head').append('<style id="fix_blueprint" type="text/css"></style>')
	
	// Add the background grid div
	$('#editor').prepend('<div id="dynamic_grid" class="container"></div>');
	
	// Generate the default CSS & background grid
	fixBlueprintCSS(g.bp.column_count, g.bp.column_width, g.bp.gutter_width);
	
	/*$('#submit').click(function(){
		fixBlueprintCSS($('#column_count').val(), $('#column_width').val(), $('#gutter_width').val());
		return false;
	});*/
}

function fixBlueprintCSS(cc, cw, gw) {
	console.log('Fixing blueprint...');
	// cc and cw cannot be 0
	if (cc == 0)
		cc = 1;
	if (cw == 0)
		cw = 1;

	// Recalculate global variables
	g.bp.column_count = cc*1;
	g.bp.column_width = cw*1;
	g.bp.gutter_width = gw*1;
	g.bp.page_width = (g.bp.column_count - 1) * (g.bp.column_width + g.bp.gutter_width) + g.bp.column_width;
	
	// Log variables
	console.log('column_count '+g.bp.column_count);
	console.log('column_width '+g.bp.column_width);
	console.log('gutter_width '+g.bp.gutter_width);
	console.log('page_width '+g.bp.page_width);
	
	
	
	//
	// STYLES THAT REQUIRE ITERATION
	//
	
	
	var spacing;
	var css = '';
	for (var i = 1; i <= g.bp.column_count; i++) {
		
		
		// spacing -- width of an n-wide span, commonly used calculation
		spacing = ((i) * (g.bp.column_width + g.bp.gutter_width));
		
		
		// span float&gutter, pull, push, input&textarea
		css+= 'div.span-'+i+'{float:left; margin-right:'+ g.bp.gutter_width + 'px;}\t';
		css+= '.pull-'+i+'{margin-left: -' + spacing + 'px;}\t';
		css+= '.push-'+i+'{margin : 0 -' + spacing + 'px 1.5em ' + spacing + 'px;}   \t';
		css+= 'input.span-'+i+', textarea.span-'+i+'{width :'+ ((g.bp.column_width + g.bp.gutter_width) * (i - 1) + g.bp.column_width - 2*(g.bp.input_padding + g.bp.input_border))+'px!important;}   \t';
		
		
		// non width-based calculations: pull, push
		css+= '.pull-'+i+'{float:left; position:relative;}   \t';
		css+= '.push-'+i+'{float:right; position:relative;} \t';
		
		// input_border/padding based calculations
		css+= 'input.span-'+i+', textarea.span-'+i+'{border-left-width:'+ g.bp.input_border +'px!important; border-right-width:'+ g.bp.input_border +'px!important; padding-left:'+ g.bp.input_padding +'px!important; padding-right:'+ g.bp.input_padding +'px!important;}\t';
		
		// check if last column
		if (i == g.bp.column_count) {
			// last column special cases
			
			// span with built in last class, container (page_width)
			css+= '.span-'+i+', div.span-'+i+'{width :'+ g.bp.page_width +'px; margin:0;}\t';
			css+= '.container{width :'+ g.bp.page_width +'px;}\t\t\t\t\t';
		} else {
			// generate for all but last column
			
			// span width, append, prepend
			css+= '.span-'+i+'{width :'+ (spacing - g.bp.gutter_width) + 'px;}\t\t';
			css+= '.append-'+i+'{padding-right :'+ spacing + 'px;}  \t';
			css+= '.prepend-'+i+'{padding-left :'+ spacing + 'px;}  \t';
		}
		
		// ADD CUSTOM EDITOR CLASSES
		css+= '.left-'+(i - 1)+'{left :'+ ((i - 1) * colUnit()) + 'px;}\t';
		css+= '.top-'+(i - 1)+'{top :'+ ((i - 1) * rowUnit()) + 'px;}\t';
		css+= '.height-'+i+'{height :'+ (i * rowUnit() - g.bp.gutter_height) +'px} \t';
		
		// ADD CUSTOM RELATIVE CLASSES
		css+= '.mleft-'+(i - 1)+'{margin-left :'+ ((i - 1) * colUnit()) + 'px;}\t';
		css+= '.mright-'+(i - 1)+'{margin-right :'+ ((i - 1) * colUnit()) + 'px;}   \t';
		css+= '.mtop-'+(i - 1)+'{margin-top :'+ ((i - 1) * rowUnit()) + 'px;}\t';
		css+= '\n';
		}
	
	
	//
	// STYLES THAT DON'T REQUIRE ITERATION
	//
	
	// Column
	css+= '.column{float:left; margin-right :'+ g.bp.gutter_width + 'px;}\n';
	
	
	
	// Borders
	css+= 'div.border{ padding-right:'+ (g.bp.gutter_width * 0.5 - 1) +'px; margin-right:'+ (g.bp.gutter_width * 0.5) +'px; border-right: 1px solid #eee;}\n';

	css+= 'div.colborder{ padding-right:'+ (g.bp.column_width + 2*g.bp.gutter_width - 1)/2 +'px; margin-right:'+ (g.bp.column_width + 2 * g.bp.gutter_width)/2 +'px; border-right: 1px solid #eee;}\n';
	
	
	// Last
	css+= '.last, div.last {margin-right:0;}';
	

	//
	// Fix resizable grid
	//		At some point, may account for namespaces
	//
	fixResizableGrid();
	
	//
	// Fix blueprint background grid
	//
	fixBlueprintGrid();
	
	$('#fix_blueprint').html(css);
}

function fixResizableGrid() {
	// Resets the grid that all '.resizable' snap to
	$('.resizable').resizable('option', 'grid', [colUnit(), rowUnit()])
		.draggable('option', 'grid', [colUnit(), rowUnit()]);
}

function fixBlueprintGrid() {
	var html = '';
	for(var i = 0; i < g.bp.column_count; i++) {
		html+='<div class="span-1 grid_column"></div>';
	}
	$('#dynamic_grid').html(html);
	$('.grid_column').filter(':last').addClass('last ui-corner-right')
		.end().filter(':first').addClass('ui-corner-left');
}


/* ---------------------------------------------------


		H  E  L  P  E  R  S
		
					
--------------------------------------------------- */ 

function multiArray(rows, cols) {
	var a = new Array(rows);
	for (var i = 0; i < rows; i++) {
		a[i] = new Array (cols);
		for (var j = 0; j < cols; j++) {
			a[i][j] = '';
		}
	}
	return a;
}

function colUnit() {
	return g.bp.column_width + g.bp.gutter_width;
}

function rowUnit() {
	return g.bp.row_height + g.bp.gutter_height;
}

function pxToCol(n) {
	return n / colUnit();
}

function pxToRow(n) {
	return n / rowUnit();
}

function colToPx(n) {
	return n * colUnit();
}

function rowToPx(n) {
	return n * rowUnit();
}

function widthToCol(n) {
	return pxToCol(n + g.bp.gutter_width);
}

function heightToRow(n) {
	return pxToRow(n + g.bp.gutter_height);
}

function colToWidth(n) {
	return colToPx(n) - g.bp.gutter_width;
}

function rowToHeight(n) {
	return rowToPx(n) - g.bp.gutter_height;
}

function nameToId( name ) {
	var id = name.toString().replace(/[ _/]/g, '-')
		.replace(/[^a-zA-Z0-9\-]/g,'')
		.toLowerCase();

	return id;
}

function sanitizeName( name ) {
	return name.replace(/[<>&'"]/g, '');
}

function resolveDuplicates( name, check ) {
	while ( check( name ) ) { 	// If name is taken, add a number to the end.
		suffix = name.match(/\d+$/g);
		if( suffix ) {	// If that number is taken, increase it and try again.
			suffix = parseInt(suffix, 10) + 1;
			name = name.replace(/\d+$/g, suffix );
		} else {
			name += ' 1';
		}
	}
	return name;
}

//Complete closure
})(jQuery)